package com.podmanager.audio;

import java.io.File;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import com.podmanager.UI.PodManager;
import com.podmanager.UI.R;
import com.podmanager.data.DBContentProvider;
import com.podmanager.data.PMDatabase;
import com.podmanager.data.Playlist;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class MusicService extends Service implements MediaPlayer.OnPreparedListener, 
							MediaPlayer.OnErrorListener, AudioManager.OnAudioFocusChangeListener, MediaPlayer.OnCompletionListener,
							MediaPlayer.OnInfoListener
{
    public enum State { 
    	Error, 
    	End,
    	Complete,
    	Stopped,
    	Idle, 
    	Initialized,
    	Preparing,
    	Prepared,
    	Started,
    	Paused};
	private State state = State.Idle;
	public static final String ACTION_INITIATE			= "com.podmanager.audio.INITIATE";
    public static final String ACTION_PLAY		 		= "com.podmanager.audio.PLAY";
    public static final String ACTION_PLAY_THIS	 		= "com.podmanager.audio.PLAY_THIS";
    public static final String ACTION_PLAYPAUSE			= "com.podmanager.audio.PLAYPAUSE";
	public static final String ACTION_PAUSE				= "com.podmanager.audio.PAUSE";
	public static final String ACTION_PREV				= "com.podmanager.audio.PREVIOUS";
	public static final String ACTION_REWIND			= "com.podmanager.audio.REWIND";
	public static final String ACTION_FFWD				= "com.podmanager.audio.FFWD";
	public static final String ACTION_NEXT				= "com.podmanager.audio.NEXT";
	public static final String ACTION_STOP				= "com.podmanager.audio.STOP";
	public static final String ACTION_SET_POSITION		= "com.podmanager.audio.SET_POSITION";
	public static final String ACTION_KILL				= "com.podmanager.audio.KILL";
	public static final String FILE_TO_PLAY				= "NEWFILE";
	private ComponentName BT_RECEIVER =null;
	
	private String mLastAction = null;
	public static final int SKIP_FWD_SECONDS	= 30;
	public static final int SKIP_BACK_SECONDS	= 30;
	private String currentFile=null;
	

	private MediaPlayer mMediaPlayer = null;
	public MediaPlayer getmMediaPlayer() {
		return mMediaPlayer;
	}

	private AudioManager mAudioManager=null;
	private String TAG="MediaService";
	private NotificationManager mNM;
    private final IBinder mBinder = new LocalBinder();
	private int NOTIFICATION_ID=1;
	private StateChangeListener stateChangeListener;
	private Notification mNotification;
	private Playlist playlist;

    
	@SuppressWarnings("deprecation")
	@Override
	public void onCreate() {
		super.onCreate();
		Bundle b =new Bundle();
		b.putString("Screen", "Play");
		Intent intent = new Intent(getApplicationContext(), PodManager.class);
		intent.putExtras(b);
		PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,intent,
		        PendingIntent.FLAG_UPDATE_CURRENT);
		
		mNotification = new Notification();
		mNotification.tickerText = "Playback Service is Running";
		mNotification.icon = R.drawable.ic_launcher;
		mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
		mNotification.setLatestEventInfo(getApplicationContext(), "Podmanager",
		                "Playback Service is Running", pi);
		// Playlist playlist = Playlist.getPlaylist();
		BT_RECEIVER= new ComponentName(getApplicationContext(), MusicIntentReceiver.class);
	}



    private boolean isPrepared(){
    	return this.state.ordinal()>=State.Prepared.ordinal();}

	public int onStartCommand(Intent intent, int flags, int startId) {
        int returnVal=0;
        mLastAction = intent.getAction();
        if (mLastAction.equals(ACTION_INITIATE)) {
        	doFirstTimeSetup();
        	returnVal= initiateFile(getCurrentFile());
        }/*
        else if (isPrepared()==false) {
        	Log.d(this.TAG, "not prepared, but attempting"+ mLastAction);
        }*/
        else if (mLastAction.equals(ACTION_PLAY)){
        	returnVal=play();
        }
        else if (mLastAction.equals(ACTION_PLAY_THIS)){
        	//if request to play a specific file is sent, do first time setup, 
        	//set passed file as current...initiate & play 
        	doFirstTimeSetup();
        	Bundle b = intent.getExtras();
        	String file=b.getString("NEWFILE");
        	setCurrentFile(file);
        	initiateFile(getCurrentFile());
       // 	returnVal=play();
        }
        else if (mLastAction.equals(ACTION_PLAYPAUSE)){
        	returnVal=playpause();
        }
        else if (mLastAction.equals(ACTION_PAUSE)){
        	returnVal=pause();
        }
        else if (mLastAction.equals(ACTION_STOP)){
        	returnVal=stop();
        }
        else if (mLastAction.equals(ACTION_SET_POSITION)){
        	returnVal=setPosition(intent.getExtras());
        }
        else if (mLastAction.equals(ACTION_PREV)){
        	returnVal=prev();
        }
        else if (mLastAction.equals(ACTION_REWIND)){
        	returnVal=rewind();
        }
        else if (mLastAction.equals(ACTION_FFWD)){
        	returnVal=ffwd();
        }
        else if (mLastAction.equals(ACTION_NEXT)){
        	returnVal=next();
        	play();
        }
        else if (mLastAction.equals(ACTION_KILL)){
        	if (!this.mMediaPlayer.isPlaying())
        	{
 	    	   	mAudioManager.unregisterMediaButtonEventReceiver(BT_RECEIVER);
        		this.stopForeground(true);
        		stopSelf();
        	}
        }
        if(isPrepared())
            storePosition();
        return returnVal;
    }
	
	private int play() {
		mLastAction=MusicService.ACTION_PLAY;
		this.doFirstTimeSetup();
		if(this.isPrepared())
		{
			if(!this.mMediaPlayer.isPlaying())
				this.mMediaPlayer.start();
			setState(State.Started);
			this.mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
	    		    AudioManager.AUDIOFOCUS_GAIN);
			mAudioManager.registerMediaButtonEventReceiver(BT_RECEIVER);
			startForeground(NOTIFICATION_ID, mNotification);
		}
		else
		{
			String file = getCurrentFile();
			if (file != null)
				initiateFile(file);
			// initiateFile(getCurrentFile());
		}
		return 0;
	}

	private int playpause() {
		if(this.mMediaPlayer.isPlaying())
			pause();
		else
			play();
		return 0;
	}
    
	private int pause() {
		mLastAction=MusicService.ACTION_PAUSE;
		if(this.mMediaPlayer.isPlaying())
			this.mMediaPlayer.pause();
		AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
		Intent receiver	= new Intent(this,MusicIntentReceiver.class);
		receiver.setAction(MusicService.ACTION_KILL);
		long time=GregorianCalendar.getInstance(TimeZone.getDefault()).getTimeInMillis();
		time+=1000*60*5;
		PendingIntent pendingStop = PendingIntent.getBroadcast(this, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT);
		alarmManager.set(AlarmManager.RTC_WAKEUP, time, pendingStop);
		
		setState(State.Paused);
		return 0;
	}

	private int stop() {
		this.doFirstTimeSetup();
        this.mMediaPlayer.stop();
        mAudioManager.abandonAudioFocus(this);
    	this.stopForeground(true);
		setState(State.Stopped);
		return 0;
	}
	
    private int setPosition(Bundle bundle) {
    	if(bundle==null)return -55;
    	int position=bundle.getInt("POSITION");
		int returnVal= setPosition(position);
		return returnVal;
	}

	private int prev() {
		return setPosition(0);
	}
	private int rewind() {
		return setPosition(SKIP_BACK_SECONDS*-1000, true);
	}
	private int ffwd() {
		return setPosition(SKIP_FWD_SECONDS*1000, true);
	}
	private int next() {
		//by setting position just under duration, next action should be that 
		//oncompletion is called by mMediaPlayer cuz this file will complete in a fraction of a second
		return setPosition(this.mMediaPlayer.getDuration()-1);
	}
	
	private void moveToNextFile() {

    	setCurrentFile(playlist.getNextItem());
		playlist.updateCurrentItem(getContentResolver(), currentFile);
	}

	private int setPosition(int position, boolean isAdjustment) {
		if (this.mMediaPlayer==null)return -20;
		if(isAdjustment)position+=this.mMediaPlayer.getCurrentPosition();
		return setPosition(position);
	}
	private int setPosition(int position)
	{
		if (this.mMediaPlayer==null)return -30;
		Log.d("service before", this.mMediaPlayer.getCurrentPosition()+"/"+this.mMediaPlayer.getDuration());
		int duration = this.mMediaPlayer.getDuration();
		if (position<0)
			position=0;
		if(position>duration)
			position=duration-1;
		this.mMediaPlayer.seekTo(position);
		Log.d("service after", this.mMediaPlayer.getCurrentPosition()+"/"+this.mMediaPlayer.getDuration());
		return 0;
	}
	
	public int getDuration()
	{
		if (mMediaPlayer==null)return 0;
		if(state.ordinal()<State.Prepared.ordinal())return 0;
		return this.mMediaPlayer.getDuration();
	}

	private void doFirstTimeSetup()
	{
    	if(this.mMediaPlayer==null)
    	{
        	mMediaPlayer = new MediaPlayer();
        	mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            mMediaPlayer.setOnErrorListener(this);
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.setOnCompletionListener(this);
            
    	}
    	if(mAudioManager==null)
    	{
    		mAudioManager  = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    		int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
    		    AudioManager.AUDIOFOCUS_GAIN);

    		if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
    		}
    	}
		mAudioManager.registerMediaButtonEventReceiver(BT_RECEIVER);
	}
	private int initiateFile(String file) {
		if(file==null)return -1;
		File f = new File (file);
		if(!f.exists())return -2;

		f.setReadable(true, false);
    	Uri myUri = Uri.fromFile(new File(file)); // initialize Uri here
    	try {
    		if(state!=State.Idle)
    			reset();
			mMediaPlayer.setDataSource(getApplicationContext(), myUri);
			setState(State.Initialized);
		} catch (Exception e) {
			e.printStackTrace();
			return -10;
		}
		
        mMediaPlayer.prepareAsync(); // prepare async to not block main thread
		setState(State.Preparing);
        return 0;
	}

	public void setCurrentFile(String currentFile) {
		this.currentFile = currentFile;
	}
	public String getCurrentFile() {
    	try{
			if (this.currentFile != null)
				return this.currentFile;
			else if (this.currentFile==null)
			{
				playlist = Playlist.getPlaylist();
				//passing null as sort value.  Playlist should be determining that itself, or what's the point of the playlist
				playlist.updatePlaylist(getContentResolver(), null);
				if (!playlist.isPlaylistEmpty())
				{
					currentFile = playlist.getFirstItem();
					if(currentFile==null)
					{
						currentFile=playlist.queryCurrentItem(getContentResolver());
					}
				}
				else 
					currentFile = "";
			}
    	}
    	catch(Exception e)
    	{
			e.printStackTrace();
    	}
    	
		return currentFile;
	}
    
	
	@Override
	public boolean onUnbind(Intent intent) {
		Log.d(TAG, "MusicService onUnbind");
		return super.onUnbind(intent);
	}
    public class LocalBinder extends Binder {
        public MusicService getService() {
            return MusicService.this;
        }
    }
	@Override
	public IBinder onBind(Intent intent) {
        return mBinder;
	}
	
	
	/** onInfo param values and meanings
	 * @param 	mp	the MediaPlayer the info pertains to.
		@param	what	the type of info or warning.
					MEDIA_INFO_UNKNOWN					1
					MEDIA_INFO_VIDEO_TRACK_LAGGING		700
					MEDIA_INFO_VIDEO_RENDERING_START	3
					MEDIA_INFO_BUFFERING_START			701
					MEDIA_INFO_BUFFERING_END			702
					MEDIA_INFO_BAD_INTERLEAVING			800
					MEDIA_INFO_NOT_SEEKABLE				801
					MEDIA_INFO_METADATA_UPDATE			802
		@param	extra	an extra code, specific to the info. Typically implementation dependent.
	 */
	@Override
	public boolean onInfo(MediaPlayer mp, int what, int extra) {
    	Log.d(TAG, "info occured on Action: "+this.mLastAction);
    	Log.d(TAG, "info what:  "+what);
    	Log.d(TAG, "info extra: "+extra);

		return false;
	}
	
	/**onError param values and meanings
		@param	mp  the MediaPlayer the error pertains to 
		@param	what  the type of error that has occurred: 
			MEDIA_ERROR_UNKNOWN 		1
			MEDIA_ERROR_SERVER_DIED 	100
 
		@param	extra  an extra code, specific to the error. Typically implementation dependent. 
			MEDIA_ERROR_IO 				-1004
			MEDIA_ERROR_MALFORMED 		-1007
			MEDIA_ERROR_UNSUPPORTED 	-1010
			MEDIA_ERROR_TIMED_OUT  		-110
	 */
    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
    	Log.d(TAG, "error occured on Action: "+this.mLastAction);
    	Log.d(TAG, "error what:  "+what);
    	Log.d(TAG, "error extra: "+extra);
    	setState(State.Error);
    	reset();
		return false;
    }

	@Override
	public void onAudioFocusChange(int focusChange) {
		switch (focusChange) {
        case AudioManager.AUDIOFOCUS_GAIN:
            // resume playback
            if (mMediaPlayer == null) this.doFirstTimeSetup();
            else if(mLastAction.equals(ACTION_PLAY) || mLastAction.equals(ACTION_PLAY_THIS))
            	this.play();
            break;

        case AudioManager.AUDIOFOCUS_LOSS:
            // Lost focus for an unbounded amount of time: stop playback and release media player
            this.stop();
            mMediaPlayer.release();
            mMediaPlayer = null;
            break;

        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
            // Lost focus for a short time, but we have to stop
            // playback. We don't release the media player because playback
            // is likely to resume
            pause();
            break;
		}		
	}
	

	static public void doStartMusicService(Context c, String action, Bundle bundle) {
		Intent serviceIntent = new Intent(c, MusicService.class);
		serviceIntent.setAction(action);
		if(bundle!=null)serviceIntent.putExtras(bundle);
		c.startService(serviceIntent);
	}
	
	public void onDestroy() {
			stop();
	       if (mMediaPlayer != null)
    	   {
	    	   if(this.isPrepared())
	    		   storePosition();
	    	   mMediaPlayer.release();
    	   }
	       
	       mNM.cancel(NOTIFICATION_ID);
	       if(mAudioManager!=null){
	    	   mAudioManager.abandonAudioFocus(this);
	    	   mAudioManager.unregisterMediaButtonEventReceiver(BT_RECEIVER);
	       }
	       setState(State.End);
   }

	public int getCurrentPosition() {
		return this.mMediaPlayer.getCurrentPosition();
	}

	@Override
	public void onCompletion(MediaPlayer mp) {
		if(state.ordinal()>State.Prepared.ordinal())
		{
			String where = PMDatabase.EPISODES.FILE_PATH + "='"+this.currentFile+"'";
			ContentValues values = new ContentValues();
			values.put(PMDatabase.EPISODES.POSITION, 0);
			values.put(PMDatabase.EPISODES.IS_READ, 1);
			values.put(PMDatabase.EPISODES.IS_IN_PLAYLIST, 0);
			this.getContentResolver().update(DBContentProvider.EPISODES_URI, values, where, null);
			setState(State.Complete);

			moveToNextFile();
        	initiateFile(getCurrentFile());
        	
        	//overriding this action so that when initiation completes it starts playback
        	//
        	this.mLastAction=MusicService.ACTION_PLAY;
		}
		else
			reset();
	}

	public void reset() {
		setState(State.Idle);
		this.mMediaPlayer.reset();
	}

	public State setOnStateChangeListener(StateChangeListener listener) {
		stateChangeListener = listener;
		return state;
	}

    private void setState(State newState) {
		state=newState;
		if(stateChangeListener!=null) stateChangeListener.onStateChange(state);
		Log.d("State", state.name());
	}

	@Override
	public void onPrepared(MediaPlayer mp) {
		setState(State.Prepared);
		getStoredPosition();
		if(mLastAction.equals(MusicService.ACTION_PLAY) || mLastAction.equals(MusicService.ACTION_PLAY_THIS))
				play();
	}
	private void getStoredPosition() {
		int position=0;
		String[] projection = {PMDatabase.EPISODES.POSITION};
		Cursor c = this.getContentResolver().query(
				DBContentProvider.EPISODES_URI, projection, 
				PMDatabase.EPISODES.FILE_PATH+"='"+this.getCurrentFile()+"'", null, null);
		if(c.moveToFirst())
		{
			position=c.getInt(c.getColumnIndex(PMDatabase.EPISODES.POSITION));
		}
		c.close();
		this.setPosition(position);
	}

	private void storePosition()
	{
		try {
			ContentValues values = new ContentValues();
			values.put(PMDatabase.EPISODES.POSITION, this.getCurrentPosition());
			this.getContentResolver().update(
					DBContentProvider.EPISODES_URI, 
					values, 
					PMDatabase.EPISODES.FILE_PATH+"='"+this.getCurrentFile()+"'", 
					null);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
